package edu.ucla.cs.typecast.ping;

import java.io.*;
import java.net.*;
import java.util.*;

import edu.ucla.cs.typecast.net.NetUtil;
import edu.ucla.cs.typecast.net.TypeCastConstants;
import edu.ucla.cs.typecast.util.slf.ScriptLogReader;
import edu.ucla.cs.typecast.util.slf.ScriptLogWriter;

public class PingSenderApp {
	public static void main(String[] args) throws IOException {
		// Read config file if it exists

		final File configFile = new File("pingsender.conf");

		Map<String, String> config = new HashMap<String, String>();
		if (configFile.exists() && configFile.canRead()) {
			ScriptLogReader slr = new ScriptLogReader(configFile, true);
			config = slr.readEntry();
		}

		// Read parameters

		final InetAddress privateHost = InetAddress.getByName(ScriptLogUtil
				.readString(config, "private_host", NetUtil.getLocalHost()
						.getHostAddress()));

		final int privatePort = ScriptLogUtil.readInt(config, "private_port",
				PingUtil.randPort());

		final InetAddress publicHost = InetAddress.getByName(ScriptLogUtil
				.readString(config, "public_host",
						TypeCastConstants.DEFAULT_RMI_ADDRESS));

		final int publicPort = ScriptLogUtil.readInt(config, "public_port",
				TypeCastConstants.DEFAULT_RMI_PORT);

		final int timeout = ScriptLogUtil.readInt(config, "timeout", 3000);

		final int rate = ScriptLogUtil.readInt(config, "rate", 1);

		final int duration = ScriptLogUtil.readInt(config, "duration", 99999999);

		// Create a ping sender

		PingSenderImplForConsole ps = new PingSenderImplForConsole(privateHost,
				privatePort, publicHost, publicPort, timeout);

		// Print accepted parameters

		System.out.println("Private: " + privateHost + ":" + privatePort);
		System.out.println("Public: " + publicHost + ":" + publicPort);
		System.out.println("Timeout: " + timeout + " ms");

		// Read script file if it exists

		final File scriptFile = new File("pingsender.script");

		ScriptLogReader slr;
		if (scriptFile.exists() && scriptFile.canRead()) {
			slr = new ScriptLogReader(scriptFile, true);
		} else {
			slr = new ScriptLogReader("action = send" + "\n" + "rate = " + rate
					+ "\n" + "duration = " + duration + "\n" + "%" + "\n"
					+ "action = wait" + "\n" + "duration = " + timeout + "\n",
					true);
		}

		// Go through all script entries

		int seqNum = 0;
		while (slr.hasNextEntry()) {
			// Read a script entry

			Map<String, String> scriptEntry = slr.readEntry();
			String action = ScriptLogUtil.readString(scriptEntry, "action", "");

			System.out.println("Script entry: " + scriptEntry);

			if (action.equals("send")) {
				// Send ping at <rate> pings/sec for <duration> seconds

				int maxSeqNum = ScriptLogUtil
						.readInt(scriptEntry, "rate", rate)
						* ScriptLogUtil.readInt(scriptEntry, "duration",
								duration);
				for (int i = 1; i <= maxSeqNum; i++) {
					long startTime = System.currentTimeMillis();
					ps.ping(++seqNum);
					ps.update();
					long stopTime = System.currentTimeMillis();

					long timeToSpend = (long) 1.0
							/ ScriptLogUtil.readInt(scriptEntry, "rate", rate)
							* 1000;
					if (stopTime - startTime <= timeToSpend)
						PingUtil.sleep(timeToSpend - (stopTime - startTime));
				}
			}

			if (action.equals("wait")) {
				// Sleep for <duration> milliseconds

				PingUtil.sleep(ScriptLogUtil.readInt(scriptEntry, "duration",
						duration));
			}

			if (action.equals("exit")) {
				// Exit

				break;
			}
		}

		// Done!

		ps.dispose();
		System.out.println("Done");
	}
}

/**
 * An implementation of PingSender tailored for the "ping program".
 */

class PingSenderImplForConsole extends PingSenderImpl implements PingSender {
	private static final long serialVersionUID = 1L;

	// If not null, will log all received pongs and timeouts

	protected static final File logFile = new File("pingsender.log");

	protected long runNum = System.currentTimeMillis();

	protected boolean done = false;

	public PingSenderImplForConsole(InetAddress privateHost, int privatePort,
			InetAddress publicHost, int publicPort, int timeout)
			throws IOException {
		super(privateHost, privatePort, publicHost, publicPort, timeout);
	}

	public PingSenderImplForConsole(String privateHost, int privatePort,
			String publicHost, int publicPort, int timeout) throws IOException {
		super(privateHost, privatePort, publicHost, publicPort, timeout);
	}

	public boolean isDone() {
		return done;
	}

	public void onExpectedTimeout(int seqNum) {
		done = true;

		System.out.println(timeout
				+ " ms elapsed from the last pong. Exiting this run.");
		try {
			writeLogFile(runNum, "expected_timeout", seqNum, null, 0);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	public void onInvalidPong(InetAddress senderHost, int senderPort, int seqNum) {
		System.out.println("Received unsolicited pong from " + senderHost + ":"
				+ senderPort + ", seq # " + seqNum);
		try {
			writeLogFile(runNum, "invalid_pong", seqNum, senderHost, senderPort);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	public void onUnexpectedTimeout(int seqNum) {
		done = true;

		System.out.println(timeout
				+ " ms elapsed without a pong. Exiting this run.");
		try {
			writeLogFile(runNum, "unexpected_timeout", seqNum, null, 0);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	public void onValidPong(InetAddress senderHost, int senderPort, int seqNum) {
		System.out.println("Received pong from " + senderHost + ":"
				+ senderPort + ", seq # " + seqNum);
		try {
			writeLogFile(runNum, "valid_pong", seqNum, senderHost, senderPort);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	public void ping(int seqNum) throws IOException {
		System.out.println("Sending ping to " + publicHost + ":" + publicPort
				+ ", seq # " + seqNum);
		super.ping(seqNum);
		try {
			writeLogFile(runNum, "ping", seqNum, null, 0);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	public void setDone(boolean done) {
		this.done = done;
	}

	/**
	 * Write a line to the log file.
	 * 
	 * @param runNum
	 * @param type
	 * @param seqNum
	 * @param receiverHost
	 * @param receiverPort
	 * @throws IOException
	 */

	protected void writeLogFile(long runNum, String type, int seqNum,
			InetAddress receiverHost, int receiverPort) throws IOException {
		if (logFile == null)
			return;

		Map<String, String> entry = new TreeMap<String, String>();
		entry.put("ses_num", Long.toString(runNum));
		entry.put("time", Long.toString(System.currentTimeMillis()));
		entry.put("type", type);
		entry.put("seq_num", Integer.toString(seqNum));
		if (receiverHost != null)
			entry.put("receiver_host", receiverHost.getHostAddress());
		if (receiverPort > 0)
			entry.put("receiver_port", Integer.toString(receiverPort));
		entry.put("public_host", publicHost.getHostAddress());
		entry.put("public_port", Integer.toString(publicPort));

		ScriptLogWriter slw = new ScriptLogWriter(logFile, true);
		slw.writeEntry(entry);
		slw.close();
	}
}
